<%#
 Copyright 2013-2025 the original author or authors from the JHipster project.

 This file is part of the JHipster project, see https://www.jhipster.tech/
 for more information.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-%>
package <%= packageName %>.config;

import <%= packageName %>.security.*;
import tech.jhipster.config.JHipsterProperties;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
<%_ if (devDatabaseTypeH2Any) { _%>
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import tech.jhipster.config.JHipsterConstants;
<%_ } _%>
<%_ if (authenticationTypeJwt) { _%>
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
<%_ } _%>
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
<%_ if (!skipClient) { _%>
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
<%_ } _%>
import org.springframework.security.web.SecurityFilterChain;
<%_ if (!skipClient || authenticationUsesCsrf) { _%>
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
<%_ } _%>
<%_ if (!skipClient) { _%>
import <%= packageName %>.web.filter.SpaWebFilter;
<%_ } _%>
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
<%_ } _%>
<%_ if (authenticationTypeJwt || (authenticationTypeOauth2 && applicationTypeMicroservice)) { _%>
import org.springframework.security.config.http.SessionCreationPolicy;
<%_ } _%>
<%_ if (generateUserManagement) { _%>
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
<%_ } _%>
<%_ if (authenticationTypeOauth2) { _%>
import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.PREFERRED_USERNAME;

import <%= packageName %>.security.oauth2.AudienceValidator;
import <%= packageName %>.security.SecurityUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
  <%_ if (!applicationTypeMicroservice) { _%>
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
  <%_ } _%>
import java.util.*;
<%_ } _%>
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
import java.util.function.Supplier;
<%_ } _%>
<%_ if (authenticationTypeSession) { _%>
import org.springframework.http.HttpStatus;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
  <%_ if (generateUserManagement) { _%>
import org.springframework.security.web.authentication.RememberMeServices;
  <%_ } _%>
<%_ } _%>
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
import org.springframework.security.web.csrf.*;
<%_ } _%>
<%_ if (authenticationTypeJwt) { _%>
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
<%_ } _%>
<%_ if (authenticationTypeOauth2 && !applicationTypeMicroservice) { _%>
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.boot.web.client.RestTemplateBuilder;
import <%= packageName %>.security.oauth2.CustomClaimConverter;
<%_ } _%>
<%_ if(!skipClient) { _%>
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
<%_ } _%>
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
import org.springframework.util.StringUtils;
<%_ } _%>
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.AntPathMatcher;

<%_ if (authenticationTypeOauth2 || !applicationTypeMicroservice || authenticationTypeJwt) { _%>
import static org.springframework.security.config.Customizer.withDefaults;
<%_ } _%>

@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfiguration {
<%_ if (devDatabaseTypeH2Any) { _%>

    private final Environment env;
<%_ } _%>

    private final JHipsterProperties jHipsterProperties;
<%_ if (authenticationTypeSession && generateUserManagement) { _%>

    private final RememberMeServices rememberMeServices;
<%_ } _%>
<%_ if (authenticationTypeOauth2) { _%>

    @Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}")
    private String issuerUri;
    <%_ } _%>

    public SecurityConfiguration(<% if (devDatabaseTypeH2Any) { %>Environment env, <% } %><% if (authenticationTypeSession && generateUserManagement) { %>RememberMeServices rememberMeServices, <% } %> JHipsterProperties jHipsterProperties) {
<%_ if (devDatabaseTypeH2Any) { _%>
        this.env = env;
<%_ } _%>
<%_ if (authenticationTypeSession && generateUserManagement) { _%>
        this.rememberMeServices = rememberMeServices;
<%_ } _%>
        this.jHipsterProperties = jHipsterProperties;
    }
<%_ if (generateUserManagement) { _%>

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
<%_ } _%>

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
<%_ if (!applicationTypeMicroservice) { _%>
            .cors(withDefaults())
<%_ } _%>
            .csrf(csrf -> csrf
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()))
<%_ } else { _%>
                .disable())
<%_ } _%>
<%_ if (!skipClient) { _%>
            .addFilterAfter(new SpaWebFilter(), BasicAuthenticationFilter.class)
<%_ } _%>
<%_ if (!skipClient) { _%>
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp.policyDirectives(jHipsterProperties.getSecurity().getContentSecurityPolicy()))
                .frameOptions(FrameOptionsConfig::sameOrigin)
                .referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
                .permissionsPolicyHeader(permissions ->
                    permissions.policy("camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()")))
<%_ } _%>
            .authorizeHttpRequests(authz ->
                // prettier-ignore
                authz
<%_ if (!skipClient) { _%>
                    .requestMatchers("/index.html", "/*.js", "/*.txt", "/*.json", "/*.map", "/*.css").permitAll()
                    .requestMatchers("/*.ico", "/*.png", "/*.svg", "/*.webapp").permitAll()
  <%_ if (clientBundlerVite) { _%>
                    .requestMatchers("/assets/**").permitAll()
  <%_ } else if (clientBundlerEsbuild) { _%>
                    .requestMatchers("/content/**").permitAll()
                    .requestMatchers("/resources/**").permitAll()
  <%_ } else { _%>
                    .requestMatchers("/app/**").permitAll()
                    .requestMatchers("/i18n/**").permitAll()
                    .requestMatchers("/content/**").permitAll()
  <%_ } _%>
                    .requestMatchers("/swagger-ui/**").permitAll()
<%_ } _%>
<%_ if (authenticationTypeJwt) { _%>
                    .requestMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
                    .requestMatchers(HttpMethod.GET, "/api/authenticate").permitAll()
<%_ } else { _%>
                    .requestMatchers("/api/authenticate").permitAll()
<%_ } _%>
<%_ if (generateUserManagement) { _%>
                    .requestMatchers("/api/register").permitAll()
                    .requestMatchers("/api/activate").permitAll()
                    .requestMatchers("/api/account/reset-password/init").permitAll()
                    .requestMatchers("/api/account/reset-password/finish").permitAll()
<%_ } _%>
<%_ if (authenticationTypeOauth2) { _%>
                    .requestMatchers("/api/auth-info").permitAll()
<%_ } _%>
                    .requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
                    .requestMatchers("/api/**").authenticated()
<%_ if (applicationTypeGateway) { _%>
  <%_ if (microfrontend) { _%>
                    // microfrontend resources are loaded by webpack without authentication, they need to be public
    <%_ if (clientBundlerVite) { _%>
                    .requestMatchers("/services/*/assets/**").permitAll()
    <%_ } _%>
                    .requestMatchers("/services/*/*.js").permitAll()
                    .requestMatchers("/services/*/*.txt").permitAll()
                    .requestMatchers("/services/*/*.json").permitAll()
                    .requestMatchers("/services/*/*.js.map").permitAll()
  <%_ } _%>
                    .requestMatchers("/services/*/management/health/readiness").permitAll()
                    .requestMatchers("/services/*/v3/api-docs").hasAuthority(AuthoritiesConstants.ADMIN)
                    .requestMatchers("/services/**").authenticated()
<%_ } _%>
<%_ if (communicationSpringWebsocket) { _%>
                    .requestMatchers("/websocket/**").authenticated()
<%_ } _%>
                    .requestMatchers("/v3/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
                    .requestMatchers("/management/health").permitAll()
                    .requestMatchers("/management/health/**").permitAll()
                    .requestMatchers("/management/info").permitAll()
                    .requestMatchers("/management/prometheus").permitAll()
                    .requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN))
<%_ if (authenticationTypeSession) { _%>
  <%_ if (generateUserManagement) { _%>
            .rememberMe(rememberMe ->
                rememberMe
                    .rememberMeServices(rememberMeServices)
                    .rememberMeParameter("remember-me")
                    .key(jHipsterProperties.getSecurity().getRememberMe().getKey())
            )
  <%_ } _%>
            .exceptionHandling(exceptionHanding -> {
                AntPathMatcher pathMatcher = new AntPathMatcher();
  <%_ if (communicationSpringWebsocket) { _%>
                RequestMatcher websocketRequestMatcher = request -> pathMatcher.match("/websocket/**", request.getRequestURI());
  <%_ } _%>
                RequestMatcher apiRequestMatcher = request -> pathMatcher.match("/api/**", request.getRequestURI());
                exceptionHanding.defaultAuthenticationEntryPointFor(
                    new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
                    new OrRequestMatcher(
  <%_ if (communicationSpringWebsocket) { _%>
                        websocketRequestMatcher,
  <%_ } _%>
                        apiRequestMatcher
                    )
                );
            })
            .formLogin(formLogin ->
                formLogin
                    .loginPage("/")
                    .loginProcessingUrl("/api/authentication")
                    .successHandler((request, response, authentication) -> response.setStatus(HttpStatus.OK.value()))
                    .failureHandler((request, response, exception) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))
                    .permitAll()
            )
            .logout(logout ->
                logout.logoutUrl("/api/logout").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).permitAll());
<%_ } else if (authenticationTypeJwt) { _%>
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .exceptionHandling((exceptions) -> exceptions
				.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
				.accessDeniedHandler(new BearerTokenAccessDeniedHandler()))
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
<%_ } else if (authenticationTypeOauth2) { _%>
  <%_ if (!applicationTypeMicroservice) { _%>
            .oauth2Login(oauth2 -> oauth2.loginPage("/").userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())))
  <%_ } else { _%>
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  <%_ } _%>
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(authenticationConverter())))
            .oauth2Client(withDefaults());
<%_ } _%>
<%_ if (devDatabaseTypeH2Any) { _%>
        if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
            http
  <%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>
                .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**"))
  <%_ } _%>
                .authorizeHttpRequests(authz -> authz.requestMatchers("/h2-console/**").permitAll());
        }
<%_ } _%>
        return http.build();
    }

<%_ if (authenticationTypeOauth2) { _%>

    Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
            new Converter<Jwt, Collection<GrantedAuthority>>() {
                @Override
                public Collection<GrantedAuthority> convert(Jwt jwt) {
                    return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims());
                }
            }
        );
        jwtAuthenticationConverter.setPrincipalClaimName(PREFERRED_USERNAME);
        return jwtAuthenticationConverter;
    }
    <%_ if (!applicationTypeMicroservice) { _%>

    OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();

        return userRequest -> {
            OidcUser oidcUser = delegate.loadUser(userRequest);
            return new DefaultOidcUser(oidcUser.getAuthorities(), oidcUser.getIdToken(), oidcUser.getUserInfo(), PREFERRED_USERNAME);
        };
    }

    /**
     * Map authorities from "groups" or "roles" claim in ID Token.
     *
     * @return a {@link GrantedAuthoritiesMapper} that maps groups from
     * the IdP to Spring Security Authorities.
     */
    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return authorities -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                // Check for OidcUserAuthority because Spring Security 5.2 returns
                // each scope as a GrantedAuthority, which we don't care about.
                if (authority instanceof OidcUserAuthority) {
                    OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
                    mappedAuthorities.addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims()));
                }
            });
            return mappedAuthorities;
        };
    }
  <%_ } _%>

    @Bean
    JwtDecoder jwtDecoder(<%_ if (!applicationTypeMicroservice) { _%>ClientRegistrationRepository clientRegistrationRepository, RestTemplateBuilder restTemplateBuilder<%_ } _%>) {
        NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience());
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);
  <%_ if (!applicationTypeMicroservice) { _%>
        jwtDecoder.setClaimSetConverter(new CustomClaimConverter(clientRegistrationRepository.findByRegistrationId("oidc"), restTemplateBuilder.build()));
  <%_ } _%>

        return jwtDecoder;
    }
<%_ } _%>
<%_ if (authenticationUsesCsrf && !applicationTypeMicroservice) { _%>

    /**
     * Custom CSRF handler to provide BREACH protection for Single-Page Applications (SPA).
     *
     * @see <a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa">Spring Security Documentation - Integrating with CSRF Protection</a>
     * @see <a href="https://github.com/jhipster/generator-jhipster/pull/25907">JHipster - use customized SpaCsrfTokenRequestHandler to handle CSRF token</a>
     * @see <a href="https://stackoverflow.com/q/74447118/65681">CSRF protection not working with Spring Security 6</a>
     */
    static final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
        private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
        private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler();

        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
            /*
             * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
             * the CsrfToken when it is rendered in the response body.
             */
            this.xor.handle(request, response, csrfToken);

            // Render the token value to a cookie by causing the deferred token to be loaded.
            csrfToken.get();
        }

        @Override
        public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
            /*
             * If the request contains a request header, use CsrfTokenRequestAttributeHandler
             * to resolve the CsrfToken. This applies when a single-page application includes
             * the header value automatically, which was obtained via a cookie containing the
             * raw CsrfToken.
             */
            if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
                return this.plain.resolveCsrfTokenValue(request, csrfToken);
            }
            /*
             * In all other cases (e.g. if the request contains a request parameter), use
             * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
             * when a server-side rendered form includes the _csrf request parameter as a
             * hidden input.
             */
            return this.xor.resolveCsrfTokenValue(request, csrfToken);
        }
    }
<%_ } _%>
}
